#include "Aof.hpp"
#include "../base/Defind.hpp"
#include "../base/BaseLog.hpp"
#include "../base/FileUtil.hpp"

#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <cstring>
#include <cstdlib>
#include <sys/wait.h>
static int unblock(int fd) {
    int old_mask, mask = O_NONBLOCK;
    if ((old_mask = ::fcntl(fd, F_GETFL, 0)) < -1)
    {
        char format[] = "get mask!\n";
        error(format);
        return -1;
    }
    // set the given mask
    if (::fcntl(fd, F_SETFL, mask|old_mask) == -1)
    {   
        char format[] = "set mask!\n";
        error(format);
        return -1;
    }
    return 0;
}

using namespace DBServer::Database;

Aof::Aof(AofRwFunc log_func, int aof_write_buff_size, std::string aof_file)
:   off_set(0),
    is_begin_aof_rw(0),
    is_begin_aof_translate(0),
    aof_func(log_func),
    aof_reader(aof_file),                                     // open db.aof for initialize database
    buff_size(aof_write_buff_size),
    aof_writer(aof_file, aof_write_buff_size),     // write to db.aof
    aof(aof_reader.file_info())                                         // file state of db.aof
{                              
    aof_buf = new char[AOF_BUF_SIZE];
    ::memset(aof_buf, 0, AOF_BUF_SIZE);
}

Aof::~Aof()
{
    delete aof_buf;
}

void Aof::pseudo_client()
{
    
}

void Aof::save(std::string command)
{
    // use \n as the separator
    command += "\n";
    
    // if it is in the period of aof,
    // check that wherter child has generate a 
    // aof translation begin singal
    static char buf[1];
    if (is_begin_aof_rw && ::read(signal[0], buf, AOFRW_FIN_LEN) == AOFRW_FIN_LEN)
    {
        // do the job 
        parent_aof_rw_third_stage_begin();
        // make respond for child
        ::write(signal[1], buf, AOFRW_FIN_LEN);
        ::close(signal[0]);
        ::close(signal[1]);
    }

    if (command.size() > AOF_BUF_SIZE - off_set)
    {
        if (is_begin_aof_translate)
        {
            // don't need siganl 
            // we only neel to wait for child exit normally
            // spin to wait child signal about the translation finish
            int pid;
            ::wait(&pid);
            parent_aof_rw_third_stage_end();
        }
        flush();
    }
    ::memcpy(aof_buf+off_set, command.c_str(), command.size());
    off_set += command.size();
}

void Aof::flush()
{
    if (off_set == 0)
        return;
    aof_writer.append(aof_buf, off_set);
    // if it is doing aof rewrite, ignore this,
    // otherwise check whether it 
    // needs to be performed aof rewrite
    if (!is_begin_aof_rw) {
        aof = aof_reader.file_info();   // read db.aof
        if (aof.file_size + aof_writer.written() > AOF_FILE_MAX)
            aof_rw();
    }
    off_set = 0;
}

/* sequence chart of aof rewrite */
//  first stage : fork() a child :                  parent->call fork()
//                                                          |
//                                             parent       |           child
//                                               -----------+-------------
//                                               |                       |
//                                         close db.aof                 open base.aof
//                                         open incr.aof
//                                               |                       |
//  second stage : ------------------------------+-----------------------+---------------------
// 1.parent continue normal logic                |                       |
//      as if in the main() function             |                       |
// 2.child start to scan the database       write the command          1.scan the db img(which copy 
//      img and write into the base.aof     into incr.aof                   the parent database by fork)
//                                                                    2.write new data into base.aof
//                                               |                       |
//                                               |                       |
//  window       --------------------------------+-----------------------+----------------------------
//                                              at this stage:
//                                       parent run in the normal logic functino
//                                      while child is do scan and rewirte operation
//                                      parent and child have no intraction
//  third stage : -------------------------------+-----------------------+-------------------------
// 1. child finish rewrite and                   |                       |
//      ready to sequence the                    |                       |
//      incr.aof, so child will                  |                       |
//      generate a signal that                   |                       |
//      will parent close the                    |                       |
//      incr.aof and write to                    |                       |
//      the aof buf, when the                    |                       |
//      aof buf is full, parent                  |                       |
//      spin and wait child to finish            |                       |
//                                               |                       |
//                                   accept the child signal<-------generate a signal
//                                               |                       |
//                                         call begin()             open incr.aof
//                                        close incr.aof      append content in incr.aof to base.aof  
//                                               |                       |
//                                               |                       |
//                                if buf.size == AOF_BUF_SIZE :          |
//                                   spin;                               |
//                                   wait the ending signal;             |
//                                               |                       |
//                                               |                       |
//                                               |                  finish append 
//                                               |               rename base.aof to db.aof
//                                               |               so it will overview origianl db.aof
//                                               |                       |
//                                               |                       |
//                                               |                       |
//                                                               generate a ending singal
//                                  wake up and open db.aof <------  then exit(0)
//                                  append aof buf to db.aof             |
//                                      then return main()               |
//                                               |                       |
// fourth stage : aof finish !! year !!!------------------------------------------------------------

extern void create_file(const char *file_name);

void Aof::aof_rw()
{
    ::pipe(signal);

    // create a the base.aof in parent.
    // besause the file which child create is not permissed 
    // parent to access.
    create_file(AOF_BASE);

    int pid = ::fork();

    if (pid == 0) {     // child process
        child_aof_rw();
        ::exit(0);
    }
    // set fd to unblocking for polling
    unblock(signal[0]);
    parent_aof_rw_second_stage();
}

//
//  child
//
void Aof::child_aof_rw()
{
    /* first stage */
    {   
        // close fd which point to db.aof
        Base::Unti::UnlockAppendFile tmp_a = std::move(aof_writer); 
        Base::Unti::ReadFile tmp_r = std::move(aof_reader);
    }
    // open base.aof in parent
    aof_writer = std::move(Base::Unti::UnlockAppendFile(AOF_BASE, buff_size));

    /* second stage */
    // execute the function
    aof_func(aof_writer);
    // close base.aof
    {
        auto tmp_w = std::move(aof_writer);
    }

    /* third stage */
    /* 1. generate a signal and wait for parent respond */
    static char buf[1];
    ::write(signal[1], AOFRW_FIN_SIG, AOFRW_FIN_LEN);
    ::read(signal[0], buf, AOFRW_FIN_LEN);
    ::close(signal[0]);
    ::close(signal[1]);

    /* 2. append data which read form incr.db */
    Base::Unti::OperateFile::translate_file(AOF_INCR, AOF_BASE);

    /* 3. rename base.aof */
    Base::Unti::OperateFile::rename(AOF_BASE, AOF_FILE);
}


//
//  parent
//
/* second stage */
void Aof::parent_aof_rw_second_stage()
{
    // close fd which point to db.aof
    Base::Unti::UnlockAppendFile tmp_a = std::move(aof_writer); 
    Base::Unti::ReadFile tmp_r = std::move(aof_reader);
    // open incr.aof in parent
    aof_writer = std::move(Base::Unti::UnlockAppendFile(AOF_INCR));
}

/* third stage begin */
// when parent excute this function
// indicate that parent is accept the begining signal 
// which generate by child
void Aof::parent_aof_rw_third_stage_begin()
{
    // call begin(), begin aof translation
    begin();    
}

/* third stage end */
// said nothing , like parent_aof_rw_third_stage_begin()
void Aof::parent_aof_rw_third_stage_end()
{
    end();
}

void Aof::begin()
{
    flush();
    is_begin_aof_translate = 1;
    {
        // child begin translate
        // close fd which point to incr.aof
        Base::Unti::UnlockAppendFile tmp = std::move(aof_writer);
    }
}

void Aof::end()
{
    {
        // close history file fd and remove it
        Base::Unti::UnlockAppendFile tmp = std::move(aof_writer);
        Base::Unti::OperateFile::remove(AOF_INCR);
    }
    // reopen db.aof
    aof_writer = std::move(Base::Unti::UnlockAppendFile(AOF_FILE));
    flush();
}
